
/*
 * GreenhouseControls class
 * This class represents a Green house which has a collection of TwoTuple which holds the states
 * of various parts of the Green house.
 * It also contains methods for problems which would halt the operation of said Green house and
 * controlling the all the running Event threads.
 * The Green house class reads the events from a text file and has capability for program restore as well.
 * 
 * Each event is its own class extending from a base class called Event.
 * 
 * James Zhang
 * ID: 3069886
 * July 17, 2012
 */
//Imports the necessary java packages
import java.io.*;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.*;

public @SuppressWarnings( "serial" )
class GreenhouseControls implements Serializable
{
    //Collection of Event states
    protected ArrayList<TwoTuple<?, ?>> collection = new ArrayList<TwoTuple<?, ?>>()
    {
        
        {
            //Private members which represent each individual event status
            add( new TwoTuple<String, Boolean>( "light", false ) );
            add( new TwoTuple<String, Boolean>( "water", false ) );
            add( new TwoTuple<String, Boolean>( "fans", false ) );
            add( new TwoTuple<String, String>( "thermostat", "Day" ) );
            add( new TwoTuple<String, Boolean>( "windowok", true ) );
            add( new TwoTuple<String, Boolean>( "poweron", true ) );
            //In case of error, error code represents the error which caused shutdown
            add( new TwoTuple<String, Integer>( "errorcode", 0 ) );
        }
    };
    //Collection of Event threads
    protected transient List<Thread> threadList = new ArrayList<Thread>();
    //Collection of Events read from file
    protected ArrayList<Event> eventList = new ArrayList<Event>();
    //The crash time
    protected long crashTime = 0;

    /**
     * Method which suspends all Event threads
     */
    public void suspendAll()
    {
        for ( Thread t : threadList )
        {
            t.suspend();
        }
    }

    /**
     * Method which resumes all Event threads
     */
    public void resumeAll()
    {
        for ( Thread t : threadList )
        {
            t.resume();
        }
    }

    /**
     * Method which stops all Event threads
     */
    public void stopAll()
    {
        for ( Thread t : threadList )
        {
            t.stop();
        }
    }

    /**
     * Called when restoring a dump. Reads the eventList and runs all events
     * where threadDeath is not true.
     */
    public void restoreEvent()
    {
        for ( Event e : eventList )
        {
            if ( e != null && e.getThread() == false )
            {
                e.thread = new Thread( e );
                e.thread.start();
            }
        }
    }

    /**
     * A generic TwoTuple class which holds a key and value
     *
     * @param <A> - The key
     * @param <Object> - The value
     */
    public class TwoTuple<A, Object> implements Serializable
    {
        //The members for the key and its value
        public final A first;
        public Object second;

        /**
         * Default Constructor
         *
         * @param a - Sets the key
         * @param b - Sets the value
         */
        public TwoTuple( A a, Object b )
        {
            first = a;
            second = b;
        }

        /**
         * Accessor for the key
         *
         * @return - Returns the key
         */
        public A getKey()
        {
            return first;
        }

        /**
         * Accessor for the object
         *
         * @return - Returns the value
         */
        public Object getValue()
        {
            return second;
        }

        /**
         * Modifier for the value
         *
         * @param newvalue - Passed in new value
         */
        @SuppressWarnings( "unchecked" )
        public void setValue( java.lang.Object newvalue )
        {
            second = ( Object ) newvalue;
        }

        /**
         * Overridden toString which returns the key and value
         *
         * @return - Returns the key and value with a spacer
         */
        public String toString()
        {
            return first + " " + second;
        }
    }

    /**
     * Method which sets the value of for a specific Event key
     *
     * @param key - The key to search for
     * @param newvalue - The new value
     */
    public synchronized void setVariable( String key, Object newvalue )
    {
        for ( TwoTuple<?, ?> s : collection )
        {
            if ( s.getKey().equals( key ) )
            {
                s.setValue( newvalue );
            }
        }
    }

    /**
     * A shutdown method which sets the appropriate error code, prints to
     * console the error occurrence, writes a log file, serializes the object,
     * then proceed to shutdown.
     *
     * @param code - The error code passed in which represents which event
     * caused shutdown
     */
    public void shutdown( int code )
    {
        Date date = new Date();
        Format formatter = new SimpleDateFormat( "hh:mm:ss a" );

        setVariable( "errorcode", code );

        String errormsg = "Current time is " + formatter.format( date );

        if ( getError() == 1 )
        {
            errormsg += ". Fatal error due to window malfunction.";
        } else
        {
            if ( getError() == 2 )
            {
                errormsg += ". Fatal error due to power outage";
            }
        }

        //Writes error out to a text file named error.log
        BufferedWriter out = null;
        try
        {
            out = new BufferedWriter( new FileWriter( "error.log", true ) );
            out.write( errormsg );
            out.newLine();
            out.flush();
            System.out.println( "Error log has been written." );
        } catch ( IOException e )
        {
            System.out.println( "Error, cannot write error log file" );
        } finally
        {
            if ( out != null )
            {
                try
                {
                    out.close();
                } catch ( IOException ex )
                {
                    //Ignored
                }
            }
        }

        //Serializes object and outputs it to dump.out
        try
        {
            ObjectOutput serial = new ObjectOutputStream( new FileOutputStream( "dump.out" ) );
            serial.writeObject( this );
            serial.close();
        } catch ( IOException e )
        {
            System.out.println( "Error with object, cannot serialize" );
        }
        System.out.println( "Greenhouse has been serialized." );

        System.out.println( errormsg );
        System.out.println( "Fatal error has forced operations to shutdown. Error log created" );
        System.out.println( "Program safely terminated." );
        stopAll();
        threadList = new ArrayList<Thread>();
        eventList = new ArrayList<Event>();
    }

    /**
     * Method to return the error code member
     *
     * @return - Returns error code
     */
    public int getError()
    {
        for ( TwoTuple<?, ?> s : collection )
        {
            if ( s.getKey().equals( "errorcode" ) )
            {
                return ( Integer ) s.getValue();
            }
        }
        return 0;
    }

    /**
     * Prints out the state of the current Green house and status of the states
     */
    public void getState()
    {
        for ( TwoTuple<?, ?> s : collection )
        {
            System.out.println( s.getKey() + " status: " + s.getValue() );
        }
    }

    /**
     * This method calls the appropriate method to fix the problem
     *
     * @param errorcode - The error which caused shutdown
     * @return - Calls the appropriate fix method
     */
    public Fixable getFixable( int errorcode )
    {
        if ( errorcode == 1 )
        {
            return new FixWindow();
        } else
        {
            if ( errorcode == 2 )
            {
                return new PowerOn();
            }
        }
        return null;
    }

    /**
     * This class resets the power member to true and outputs a log on the fix
     */
    public class PowerOn implements Fixable, Serializable
    {
        /**
         * Default constructor, calls fix and log
         */
        public PowerOn()
        {
            this.fix();
            this.log();
        }

        /**
         * Resets error code and power
         */
        public void fix()
        {
            setVariable( "errorcode", 0 );
            setVariable( "poweron", true );
        }

        /**
         * Outputs a log of the fix
         */
        public void log()
        {
            Date date = new Date();
            Format formatter = new SimpleDateFormat( "hh:mm:ss a" );

            String errormsg = "Current time is " + formatter.format( date ) + ". Power was fixed.";
            BufferedWriter out = null;
            try
            {
                out = new BufferedWriter( new FileWriter( "error.log", true ) );
                out.write( errormsg );
                out.newLine();
                out.flush();
                System.out.println( errormsg );
                System.out.println( "Error log has been written." );
            } catch ( IOException e )
            {
                System.out.println( "Error, cannot write error log file" );
            } finally
            {
                if ( out != null )
                {
                    try
                    {
                        out.close();
                    } catch ( IOException ex )
                    {
                        //Ignored
                    }
                }
            }
        }
    }

    /**
     * This class resets the window member to true and outputs a log on the fix
     */
    public class FixWindow implements Fixable, Serializable
    {
        /**
         * Default constructor, calls fix and log
         */
        public FixWindow()
        {
            this.fix();
            this.log();
        }

        /**
         * Resets error code and window
         */
        public void fix()
        {
            setVariable( "errorcode", 0 );
            setVariable( "windowok", true );
        }

        /**
         * Outputs a log of the fix
         */
        public void log()
        {
            Date date = new Date();
            Format formatter = new SimpleDateFormat( "hh:mm:ss a" );

            String errormsg = "Current time is " + formatter.format( date ) + ". Window was fixed.";
            BufferedWriter out = null;
            try
            {
                out = new BufferedWriter( new FileWriter( "error.log", true ) );
                out.write( errormsg );
                out.newLine();
                out.flush();
                System.out.println( errormsg );
                System.out.println( "Error log has been written." );
            } catch ( IOException e )
            {
                System.out.println( "Error, cannot write error log file" );
            } finally
            {
                if ( out != null )
                {
                    try
                    {
                        out.close();
                    } catch ( IOException ex )
                    {
                        //Ignored
                    }
                }
            }
        }
    }

    /**
     * Called when program is incorrectly run. Prints correct usage
     */
    public static void printUsage()
    {
        System.out.println( "Correct format: " );
        System.out.println( "  java GreenhouseControls -f <filename>, or" );
        System.out.println( "  java GreenhouseControls -d dump.out" );
    }

//---------------------------------------------------------
    public static void main( String[] args )
    {
        try
        {
            //Program accepts two arguements, an option for either restore or run the Greenhouse and a filepath
            String option = args[0];
            String filename = args[1];

            //Checks for valid arguements
            if (  ! ( option.equals( "-f" ) ) &&  ! ( option.equals( "-d" ) ) )
            {
                System.out.println( "Invalid option" );
                printUsage();
            }

            //Creates new Greenhouse Object
            GreenhouseControls gc = new GreenhouseControls();

            if ( option.equals( "-f" ) )
            {
                //Adds appropriate restart event with specified file path and runs
                Restart res = new Restart( gc, 0, filename );
                res.thread.start();
            } else
            {
                if ( option.equals( "-d" ) )
                {
                    //Restores a Greenhouse Controls object from specified file path
                    new Restore( filename );
                }
            }
        } catch ( ArrayIndexOutOfBoundsException e )
        {
            System.out.println( "Invalid number of parameters" );
            printUsage();
        }
    }
} ///:~
